﻿using Tycho.Runtime;
using Tycho.Parser.Tokens;
using Tycho.Compiler;

namespace Tycho.Language {
    public class ExpressionLanguage {
        private readonly ExpressionCompiler ExpressionCompiler;
        private readonly Namespace DefaultNamespace;
        private readonly ISourceParser SourceParser;

        public ExpressionLanguage (AnyObject macroContext, Namespace defaultNamespace) {
//            SourceParser = new PegSourceParser ();
            SourceParser = new ExpressionSourceParser (macroContext);
            DefaultNamespace = defaultNamespace;
            ExpressionCompiler = new ExpressionCompiler (DefaultNamespace);
        }

        public AnyObject LoadModule (string expression, string filename, AnyObject context, IModuleScopeLoader moduleLoader) {
            var moduleScope = new FrameSymbolScope (new ModuleSymbolScope (moduleLoader));
            var scope = new FrameSymbolScope (moduleScope);
            AnyObject moduleFrame = new StackFrameObject (RuntimeModule.StackFrame, context);
            AnyObject stackFrame = new StackFrameObject (RuntimeModule.StackFrame, moduleFrame);

            AnyObject moduleInit = CompileOperation (expression, filename, scope, moduleScope, stackFrame, true);
            moduleInit.Invoke ();

            AnyObject module = RuntimeModule.CreateModule (DefaultNamespace);

            foreach (AnyObject varname in moduleFrame.Variables) {
                module.SetVariable (module, varname, 0, moduleFrame.GetVariable (moduleFrame, varname, 0));
            }

            return module;
        }

        public AnyObject CompileOperation (string expression, string filename, AnyObject context, IModuleScopeLoader moduleLoader) {
            FrameSymbolScope scope = BuildScope (context, new ModuleSymbolScope (moduleLoader));
            return CompileOperation (expression, filename, scope, null, context, false);
        }

        private AnyObject CompileOperation (string expression, string filename, FrameSymbolScope scope, FrameSymbolScope moduleScope, AnyObject context, bool atModuleLevel) {
            AnyObject terms = SourceParser.Parse (expression, filename);
            ExpressionCompiler.ModuleScope = moduleScope;

            AnyObject assembly = ExpressionCompiler.Compile (terms, scope, atModuleLevel);

            HandleErrors ();

            ClosureObject closure = CompileClosure (expression, filename, assembly, context);
            return closure;
        }

        private static ClosureObject CompileClosure (string expression, string filename, AnyObject assembly, AnyObject context) {
            var bc = new ByteCodeCompiler ();

            var sloc = new SourceLocation (expression, filename, -1, -1, 1, expression.Length - 1);
            ClosureObject closure = bc.GenerateClosure (assembly, context, sloc);

            closure.IsTopLevel = true;
            return closure;
        }

        private void HandleErrors () {
            if (ExpressionCompiler.Errors.Count > 0) {
                throw ExpressionCompiler.Errors [0];
            }
        }

        public static FrameSymbolScope BuildScopeRecursive (AnyObject frame) {
            FrameSymbolScope outerScope = null;
            AnyObject outerScopeObject = frame.OuterScope;

            if (!outerScopeObject.IsNull) {
                outerScope = BuildScopeRecursive (outerScopeObject);
            }

            return BuildScope (frame, outerScope);
        }

        private static FrameSymbolScope BuildScope (AnyObject frame, SymbolScope outerScope) {
            FrameSymbolScope scope = new FrameSymbolScope (outerScope);
            
            foreach (AnyObject variable in frame.Variables) {
                scope.DeclareVariableOverride (variable, null);
            }

            return scope;
        }
    }
}
